面向接口编程?Service Provider Interface是什么Interface 您所在的位置:网站首页 file provider是什么 面向接口编程?Service Provider Interface是什么Interface

面向接口编程?Service Provider Interface是什么Interface

2023-04-10 11:27| 来源: 网络整理| 查看: 265

什么是SPIService provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.

直译过来SPI是一个能被第三方继承或者实现的API,一般用于框架扩张或者可变组件。可能这个理解起来还是比较难,再找一个图来看看

看起来好像比较简单一点了,但是对于我在刚接触SPI的时候还是比较难懂,所以我画了一个我觉得比较适合新手同学的图

可以看到我用3个颜色来标注3个处理接口方,常规情况下,这3方一般是隶属于3个组织,或者1个组织的3个部门,转化成具体的代码就是这3方一般是属于3个工程的。

SPI实践

有了以上的基础知识,接下来再用一个例子来加深大家的了解,大体的步骤如下:

定义一个Interface规范有两个工程实现这个Interface再定义一个使用Interface的工程

具体的工程目录如下:

接口定义方

整个模块啥也不干,单纯地定义一个接口

接口实现方

接口实现方如果想实现上面定义的接口,第一步首先肯定是在pom文件中引入java-api-service 这个模块

接着也是啥也不干,单纯地实现定义的接口即可,下面是接口实现方1的代码

为了大家看得更清楚一点,我特意在一个接口实现方中定义了两个具体实现类

XxlServiceLoader

XxlOtherServiceLoader

接口实现方2的代码也是类似的

CuteyServiceLoader

注意:这两个接口实现方是隶属不同module,但是共同依赖了接口定义方

注意:这两个接口实现方是隶属不同module,但是共同依赖了接口定义方

注意:这两个接口实现方是隶属不同module,但是共同依赖了接口定义方

但是实现了接口还不行,要想实现SPI还得有一个公共的约定配置文件,在META-INF目录下新建一个services目录,有些工程创建的时候可能没有META-INF,那就得在resource目录下先新建META-INF。接着在services目录中新增一个文本文件,文件名为接口定义的全限定类名,比如上面的例子实现的接口是InterfaceService ,那文本文件名称就必须是com.cutey.none.spi.service.InterfaceService 。注意,这里的文本文件并不是指文件格式txt,而是单纯文本文件。

文本文件的内容为具体实现类的全限定类名,针对上面的例子,不同实现的文本文件内容如下:

接口实现方1com.cutey.none.spi.serviceloader.XxlOtherServiceLoadercom.cutey.none.spi.serviceloader.XxlServiceLoader接口实现方2com.cutey.none.spi.serviceloader.CuteyServiceLoader

实际文件目录及内容如图

接口使用方

有了以上接口定义和接口实现,接下来看看使用方是怎么使用的,首先使用方要使用这个接口,那肯定也是要依赖接口定义方,接着要使用具体的实现,那同样也需要依赖接口实现方。

整个工程是比较简单的,里面只有一个类似Main的方法

App类里面的内容如下,类里面的读取SPI是用java.util.ServiceLoader

看下输出是什么

可以看到在使用方中没有写任何接口实现的代码,仅仅是导入了2个依赖jar,就能使用依赖jar包中的具体实现,感兴趣的同学可以试试如果只依赖上述的单独1个jar会不会是你想的预期。

工程中的SPI

上面用一个简单的例子帮大家快速体验了下什么是SPI,现在再来简单看看身边有哪些使用SPI的例子。由wikipedia可以看到有这些使用了SPI,接下来我们挑耳熟能详的数据库和spring.factories来看看。

数据库

稍微熟悉一点的同学都知道以前是使用Class.forName("com.mysql.cj.jdbc.Driver"); 去加载驱动类的。对于mysql而言mysql8更改了驱动类的包路径,然后引入了mysql8后发现代码跑不通了还专门去网上搜为什么。所以这种方式肯定是有弊端的,浅举3个

需要手动编写驱动类地址,不管是硬编码还是配置文件的形式,都需要我们在使用的时候编写驱动类全限定类名更改驱动类不方便,需要找到定义的地方,然后再去替换当类名发生变化后需要使用方去适配,如果存在信息差那会导致程序运行不了

而在有了spi后,已经不用以上方式去加载驱动类了,而是使用DriverManager.getConnection() 去连接数据库,可以看到,连驱动类是啥都已经不关注了。当然,无论哪种形式肯定都要引用数据库的jar包,这是大前提

由于这里不是讲SPI的原理,就不深究DriverManager里面干了啥,这里就贴一张图表明确实用到了spi,感兴趣的同学可以自行研究源码。

为了让各位同学更简单直白的看到这个效果,咱新建一个工程,然后pom文件中引入mysql和oracle的依赖,编写个main方法去加载,看能不能取到两个数据库驱动类的全限定类名。

工程目录如下

好不客气地说是啥都没有,然后增加Main类中的方法

接着打断点进行代码调试,看下providerNames是不是真的有我们引入的依赖jar包了。

具体怎么做到的呢,在最开始spi实践我们自己实现的时候就知道是在src/main/resources/META-INF/services 目录下有个以接口作为文件名的文本文件,那现在就去mysql和oracle中的jar包中验证下

可以看到确实如此,看到这里就能体会到一个最最最明显的好处,那就是哪怕以后mysql的接口实现类发生了变化,我们也不用关注,因为在它本身的jar包中就提供了驱动实现类的全限定类名。

spring.factories

写在前面,spring.factories是SpringBoot的特性,而非是Spring,然后是用于自动装配(目前我了解到的是只用于这个)。所谓自动装配,说得再简单,那就是在不侵入式修改代码的情况下能够加载其他jar包内的bean

下面演示代码的整体概要:

有两个模块,java-spi-springboot-provider 和 java-spi-springboot前者包含Student的bean,后着包含Teacher的beanjava-spi-springboot要使用java-spi-springboot-provider 里的bean两个bean所在的包路径不一样,Student的全限定类名为com.xxl.cutey.none.javaspispringbootprovider.Student ,Teacher的全限定类名为com.cutey.none.xxl.javaspispringboot.Teacher java-spi-springboot依赖java-spi-springboot-provider

整体的工程目录图如下

各位小伙伴先别想着spring.factories,就正常情况下,我们是怎么使用别的jar包里面的bean的。也就是在非provider中使用Student这个bean,那首先必是要先加@Component 注解。

java-spi-springboot-provider新建一个Student类,然后在主启动类中读取这个bean

主启动类

注意,在主启动类和bean不在同一个包路径下的时候,是需要用scanBasePackage扫包才能把bean注入到IOC容器中。接下来看下输出,没问题。

java-spi-springboot新建一个Teacher和主启动类都和上面一样就不再赘述,现在是想用上面声明的bean,那就只有一个方法,修改scanBasePackages,要么是扫两个Bean的公共包,还记得吗,Student和Teacher我特意把全限定类名改得不懂;要么是具体写完这两个Bean的全限定类名。

看下输出,注意:如果Student的包名没写对,那肯定是获取不到Student的bean的,因为它是在别的jar包下面。

问题来了,如果我想要再引别的组织的包呢,要知道在真实开发中,不同部门,不同组织,甚至不同公司之间相互引用实在是太正常了,难道每次引用一个都得这么加吗,这就引出了spring.factories。

我们改造的是java-spi-springboot-provider

接下来我们再修改下java-spi-springboot中的包,把扫描的student的包去掉

我们可以神奇的发现,还是能读取出来bean

让我们浅浅看Spring用是不是真的用到了SPI

诶,好像没看到跟数据库一样用到了jdk中的ServiceLoader呀,是不是骗你们了捏,其实不是,其实SpringFactoriesLoader就是类是jdk中的ServiceLoader。说白了,spring使用的SPI的思想,而SPI准确来说也不是一个接口,而是面向接口编程的其中一种思想,是接口使用方和接口定义方约定一种面向编程的思想。

参考文献

Service provider interface - Wikipedia

10分钟让你彻底明白Java SPI,附实例代码演示#安员外很有码哔哩哔哩bilibili

Java SPI (Service Provider Interface) 机制详解 - 腾讯云开发者社区-腾讯云 (tencent.com)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有